using System;
using System.Collections.Generic;
using System.Text;

namespace System.Web
{
    class HttpUtility
    {
        public static string UrlEncode(string text)
        {
            if (string.IsNullOrEmpty(text))
            {
                return "";
            }

            byte[] rawBytes = System.Text.Encoding.UTF8.GetBytes(text);

            //check for unsafe chars
            int spaces = 0;
            int unsafeChars = 0;
            for (int i = 0; i < rawBytes.Length; i++)
            {
                char ch = (char)rawBytes[i];
                if (ch == ' ')
                {
                    spaces++;
                }
                else if (!IsSafe(ch))
                {
                    unsafeChars++;
                }
            }
            if ((spaces == 0) && (unsafeChars == 0))
            {
                //text is safe
                return Encoding.ASCII.GetString(rawBytes, 0, rawBytes.Length);
            }

            byte[] buffer = new byte[rawBytes.Length + (unsafeChars * 2)];
            int indexIntoBuffer = 0;
            for (int j = 0; j < rawBytes.Length; j++)
            {
                char thisChar = (char)rawBytes[j];
                if (IsSafe(thisChar))
                {
                    //add as-is
                    buffer[indexIntoBuffer++] = Convert.ToByte(thisChar);
                }
                else if (thisChar == ' ')
                {
                    buffer[indexIntoBuffer++] = 0x2b;
                }
                else
                {
                    //escape the unsafe character
                    buffer[indexIntoBuffer++] = 0x25;
                    buffer[indexIntoBuffer++] = Convert.ToByte(IntToHex((Convert.ToByte(thisChar) >> 4) & 15));
                    buffer[indexIntoBuffer++] = Convert.ToByte(IntToHex(Convert.ToByte(thisChar) & 15));
                }
            }
            return Encoding.ASCII.GetString(buffer, 0, buffer.Length);
        }

        private static char IntToHex(int n)
        {
            if (n <= 9)
            {
                return Convert.ToChar(n + 0x30);
            }
            return Convert.ToChar((n - 10) + 0x61);
        }

        private static bool IsSafe(char ch)
        {
            //safe characters are all alphanumerics
            if ((((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z'))) || ((ch >= '0') && (ch <= '9')))
            {
                return true;
            }
            switch (ch)
            {
                    //and specific symbols
                case '\'':
                case '(':
                case ')':
                case '*':
                case '-':
                case '.':
                case '_':
                case '!':
                    return true;
            }
            //everything else is unsafe
            return false;
        }
    }
}
